package com.simafei.flow.core.common;

import cn.hutool.core.text.CharPool;
import com.simafei.flow.core.json.JsonUtils;
import lombok.Data;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 条件生成器
 * @author fengpengju
 */
@Data
public class Criteria implements Condition {

    private Conj conj = Conj.SPACE;

    private List<Condition> conditions = new ArrayList<>();

    public Criteria() {

    }

    private Criteria(Conj conj, List<Condition> conditions) {
        this.conj = conj;
        this.conditions = conditions;
    }

    public static Criteria create() {
        return new Criteria();
    }

    public static Criteria create(Conj conj, List<Condition> conditions) {
        return new Criteria(conj, conditions);
    }

    public Criteria eq(String field, String value, CalcType calcType) {
        conditions.add(new Criterion(field, Operator.EQ, value, calcType));
        return this;
    }

    public Criteria lt(String field, String value, CalcType calcType) {
        conditions.add(new Criterion(field, Operator.LT, value, calcType));
        return this;
    }

    public Criteria le(String field, String value, CalcType calcType) {
        conditions.add(new Criterion(field, Operator.LE, value, calcType));
        return this;
    }

    public Criteria gt(String field, String value, CalcType calcType) {
        conditions.add(new Criterion(field, Operator.GT, value, calcType));
        return this;
    }

    public Criteria ge(String field, String value, CalcType calcType) {
        conditions.add(new Criterion(field, Operator.GE, value, calcType));
        return this;
    }

    public Criteria neq(String field, String value, CalcType calcType) {
        conditions.add(new Criterion(field, Operator.NEQ, value, calcType));
        return this;
    }

    public Criteria in(String field, Collection<?> value, CalcType calcType) {
        conditions.add(new Criterion(field, Operator.IN, JsonUtils.toJsonString(value), calcType));
        return this;
    }

    public Criteria notIn(String field, Collection<?> value, CalcType calcType) {
        conditions.add(new Criterion(field, Operator.NOT_IN, JsonUtils.toJsonString(value), calcType));
        return this;
    }

    public Criteria like(String field, String value, CalcType calcType) {
        conditions.add(new Criterion(field, Operator.LIKE, value, calcType));
        return this;
    }

    public Criteria notLike(String field, String value, CalcType calcType) {
        conditions.add(new Criterion(field, Operator.NOT_LIKE, value, calcType));
        return this;
    }

    public Criteria isEmpty(String field) {
        conditions.add(new Criterion(field, Operator.IS_EMPTY, null, null));
        return this;
    }

    public Criteria isNotEmpty(String field) {
        conditions.add(new Criterion(field, Operator.IS_NOT_EMPTY, null, null));
        return this;
    }

    public Criteria or(Criteria other) {
        conditions.add(new Criteria(Conj.OR, other.conditions));
        return this;
    }

    public Criteria and(Criteria other) {
        conditions.add(new Criteria(Conj.AND, other.conditions));
        return this;
    }

    public Criteria group(Criteria other) {
        conditions.add(new Criteria(Conj.SPACE, other.conditions));
        return this;
    }

    @Override
    public List<String> fields() {
        return conditions.stream().flatMap(c -> c.fields().stream()).collect(Collectors.toList());
    }

    @Override
    public String toExpr(Map<String, Object> paramMap) {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        for (int i = 0; i < conditions.size(); i++) {
            Condition condition = conditions.get(i);
            if (i > 0) {
                sb.append(CharPool.SPACE).append(condition.getConj().getValue()).append(CharPool.SPACE);
            }
            sb.append(condition.toExpr(paramMap));
        }
        sb.append(")");

        return sb.toString();
    }

    @Override
    public SqlConditionResult toSqlExpr(Map<String, Object> paramMap) {
        StringBuilder sb = new StringBuilder();
        List<String> args = new ArrayList<>();
        sb.append("(");
        for (int i = 0; i < conditions.size(); i++) {
            Condition condition = conditions.get(i);
            SqlConditionResult result = condition.toSqlExpr(paramMap);
            if (i > 0) {
                sb.append(CharPool.SPACE).append(condition.getConj().name()).append(CharPool.SPACE);
            }
            sb.append(result.getWhen());
            args.addAll(result.getArgs());
        }
        sb.append(")");
        return SqlConditionResult.builder()
                .when(sb.toString())
                .args(args)
                .build();
    }
}
